/* Copyright (C) 2011 Riccardo Marson

   This file is part of OpenPool, an open-source game of pool. 

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   RICCARDO MARSON BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
   AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

  See Readme.rtf for further information.
  
*/

#include "objects.h"

void SeparateObjects(CollisionShapePtr shape1, CollisionShapePtr shape2)
{
	/*IObject* obj1, *obj2;

	obj1 = shape1->GetParent();
	obj2 = shape2->GetParent();

	if ( ( shape1->GetType() == CS_SPHERE ) &&
			( shape2->GetType() == CS_SPHERE ) )
	{
		CVector3 distance( obj1->GetPosition() - obj2->GetPosition() );

		//find the normal unit vector
		CVector3 normal_u( distance * (1.0 / distance.GetLength() ) );

		float d = distance.GetLength();

		obj1->GetPosition() = obj1->GetPosition() + normal_u * (d/2.0);
		obj2->GetPosition() = obj2->GetPosition() + normal_u * (-d/2.0);
	}
	else if ( ( shape1->GetType() == CS_SPHERE ) &&
			( shape2->GetType() == CS_PLANE ) )

	{

		CSphere* sphere = dynamic_cast<CSphere*>(shape1.get());
		CPlane* plane = dynamic_cast<CPlane*>(shape2.get());

		CVector3 ray = plane->GetNormal();

		CVector3 tmp = obj2->GetPosition() - obj1->GetPosition();

		float dist_to_plane = plane->GetNormal().Dot(tmp);

		float check_dir = ray.Dot( obj1->GetVelocity() * (1.0 / obj1->GetVelocity().GetLength()) );

		if ( (dist_to_plane > 0 ) && (check_dir < 0) )
		{
			//collision point on plane
			CVector3 collPoint = obj1->GetPosition() + ray * dist_to_plane;
			CVector3 delta = obj1->GetPosition() - collPoint;

			float dist_to_collp = delta.GetLength();

			float d = sphere->GetRadius() - dist_to_collp;

			obj1->GetPosition() = obj1->GetPosition() - ray * d;

		}
	}
	else if ( ( shape1->GetType() == CS_PLANE ) &&
			( shape2->GetType() == CS_SPHERE ) )
	{
		CPlane* plane = dynamic_cast<CPlane*>(shape1.get());
		CSphere* sphere = dynamic_cast<CSphere*>(shape2.get());

		CVector3 ray = plane->GetNormal();

		CVector3 tmp = obj1->GetPosition() - obj2->GetPosition();

		float dist_to_plane = plane->GetNormal().Dot(tmp);

		float check_dir = ray.Dot( obj2->GetVelocity() * (1.0 / obj2->GetVelocity().GetLength()) );

		if ( (dist_to_plane > 0 ) && (check_dir < 0) )
		{
			//collision point on plane
			CVector3 collPoint = obj2->GetPosition() + ray * dist_to_plane;
			CVector3 delta = obj2->GetPosition() - collPoint;

			float dist_to_collp = delta.GetLength();

			float d = sphere->GetRadius() - dist_to_collp;

			obj2->GetPosition() = obj2->GetPosition() - ray * d;

		}
	}*/
}

/* *********************************
   CSphere
  ********************************* */

CSphere::CSphere()
{
	 mType = CS_SPHERE;
}

CSphere::CSphere( float radius )
{
    mType = CS_SPHERE;
    mRadius = radius;
}

CSphere::~CSphere()
{

}

eCollisionShape CSphere::GetType()
{
	return mType;
}

void CSphere::SetParent(IObject* parent)
{
	mParent = parent;
}

IObject* CSphere::GetParent()
{
	return mParent;
}

CollisionPair CSphere::TestCollision(CollisionShapePtr other, bool& collided)
{
	CollisionPair c_pair;
	collided = false;

	switch (other->GetType())
	{
	case CS_SPHERE:
	{
		CSphere *other_sph;
		other_sph = dynamic_cast<CSphere*>(other.get());

		CVector3 distance( mParent->GetPosition() - other_sph->mParent->GetPosition() );
		float rr = mRadius + other_sph->mRadius;

		if (distance.GetLength() <= rr )
		{
			if ( ( distance.GetLength() < rr ) &&
					( fabs( distance.GetLength() - rr ) > 0.05 ) )
				SeparateObjects( mParent->GetCollisionShape(), other );

			c_pair.first = mParent->GetCollisionShape();
			c_pair.second = other;
			collided = true;
		}
	}
		break;
	case CS_PLANE:
	{
		CPlane* plane = dynamic_cast<CPlane*>(other.get());

		//find the distance between the sphere and one point on the plane (i.e. the plane center)
		CVector3 distance( plane->mParent->GetPosition() - mParent->GetPosition() );

		//find the component of distance along the plane normal (unit vector)
		float distance_n = distance.Dot(plane->GetNormal());

		//if distance_n is less or equal to the sphere radius, a collision happens
		if ( fabs(distance_n) <= mRadius)
		{
			if ( mParent->IsFalling() )
			{
				mParent->GetVelocity() = CVector3(0.0, 0.0, mParent->GetVelocity().z() );
			}

			if ( distance_n < mRadius )
				SeparateObjects( mParent->GetCollisionShape(), other );

			c_pair.first = mParent->GetCollisionShape();
			c_pair.second = other;
			collided = true;
		}
	}
		break;
	case CS_SOCKET:
	{
		CSphere *socket;
		socket = dynamic_cast<CSphere*>(other.get());

		CVector3 distance( mParent->GetPosition() - socket->mParent->GetPosition() );

		float rr = mRadius + socket->mRadius;

		if (distance.GetLength() <= rr )
		{
			c_pair.first = mParent->GetCollisionShape();
			c_pair.second = other;

			// Collision routines will not be called in this case.
			// The "collided" flag is left to false, but the parent object state
			// is changed to "falling".

			// Nevertheless, the collision pair is returned in case we need the
			// info later.

			mParent->SetFalling(true);
		}
	}
		break;
	case CS_BOX:
	{
		CBox *cube;
		CPlane *plane;
		CVector3 offset;
		cube = dynamic_cast<CBox*>(other.get());

		for (int i = 0; i < cube->mPlanes.size(); ++i)
		{
			offset = cube->mPlanes[i].first;
			plane = dynamic_cast<CPlane*>(cube->mPlanes[i].second.get());

			CVector3 planeNormal = plane->GetNormal();
			CVector3 pointOnPlane = plane->mParent->GetPosition() + offset;

			//find the distance between the sphere and one point on the plane (i.e. the plane center)
			CVector3 distance( pointOnPlane - mParent->GetPosition() );

			//find the component of distance along the plane normal (unit vector)
			float distance_n = distance.Dot(plane->GetNormal());

			//if distance_n is less or equal to the sphere radius, a collision happens
			if ( fabs(distance_n) <= mRadius)
			{
				if ( mParent->IsFalling() )
				{
					mParent->GetVelocity() = CVector3(0.0, 0.0, mParent->GetVelocity().z() );
				}

				if ( distance_n < mRadius )
					SeparateObjects( mParent->GetCollisionShape(), cube->mPlanes[i].second );

				c_pair.first = mParent->GetCollisionShape();
				c_pair.second = cube->mPlanes[i].second;
				collided = true;
				break;
			}
		}
	}
		break;
	default:
		break;
	}

	return c_pair;
}

void CSphere::SetRadius(float radius)
{
	mRadius = radius;
}

float& CSphere::GetRadius()
{
	return mRadius;
}

/* *********************************
   CPlane
  ********************************* */
CPlane::CPlane()
{
	mType = CS_PLANE;
}

CPlane::CPlane(CVector3& normal)
{
    mNormal = normal;
    mType = CS_PLANE;
}

CPlane::~CPlane()
{

}

eCollisionShape CPlane::GetType()
{
	return mType;
}

void CPlane::SetParent(IObject* parent)
{
	mParent = parent;
}


IObject* CPlane::GetParent()
{
	return mParent;
}


CollisionPair CPlane::TestCollision(CollisionShapePtr other, bool& collided)
{
//TODO
}


void CPlane::SetNormal(CVector3& normal)
{
    mNormal = normal;
}

CVector3& CPlane::GetNormal()
{
    return mNormal;
}


/* *********************************
   CCube
  ********************************* */

CBox::CBox()
{
	mType = CS_BOX;
}

CBox::~CBox()
{

}

eCollisionShape CBox::GetType()
{
	return mType;
}

void CBox::SetParent(IObject* parent)
{
    mParent = parent;

    for (int i = 0; i < mPlanes.size(); ++i)
    {
    	mPlanes[i].second->SetParent(parent);
    }

}


IObject* CBox::GetParent()
{
	return mParent;
}

void CBox::AddPlane(CVector3& offset, CVector3& normal)
{
	CollisionShapePtr pl = CollisionShapePtr( new CPlane(normal) );
	
	pair<CVector3, CollisionShapePtr> pp(offset, pl);

	mPlanes.push_back(pp);
}

pair<CVector3, CollisionShapePtr> CBox::GetPlane(int index)
{
	return mPlanes[index];
}

CollisionPair CBox::TestCollision(CollisionShapePtr other, bool& collided)
{
//TODO
}

/* *********************************
   CEntity base class implementation
  ********************************* */

CObject::CObject()
{
	mName = "";
	mType = TYPE_STATIC;
	mID = OBJ_GENERIC;

	mPosition = CVector3(0.0,0.0,0.0);
	mVelocity = CVector3(0.0,0.0,0.0);

	// by default, no physical properties are assigned.
	mMass = 0.0;
	mCD = 0.0;
	mAe = 0.0;

	mHeading = CVector3(1.0, 0.0, 0.0); // default heading is along the x axis
	mRotationAxis = CVector3(0.0, 0.0, 1.0); // default rotation axis is z
	mRotationAngle = 0;

	mIsActive = true;
	mIsFalling = false;
	
	mScriptLink = new CScriptObject(this);
}

CObject::CObject( eObjectType type, eObjectID id, string name,
		CVector3 initialPosition, CVector3 initialVelocity )
{
	mType = type;
	mID = id;
    mName = name;
    mPosition = initialPosition;
    mVelocity = initialVelocity;

    // by default, no physical properties are assigned.
    mMass = 0.0;
    mCD = 0.0;
    mAe = 0.0;

    mHeading = CVector3(1.0, 0.0, 0.0); // default heading is along the x axis
    mRotationAxis = CVector3(0.0, 0.0, 1.0); // default rotation axis is z
    mRotationAngle = 0;

    mIsActive = true;
    mIsFalling = false;
	
	mScriptLink = new CScriptObject(this);

}

CObject::~CObject()
{
	//mScriptLink->mCodeObj = 0??
	mScriptLink->Release();
	//TODO add event to notify script of the object destruction, otherwise
	// one more reference  to the script object remains and the object will not be destroyed.
	// (but its code-side parent will).
}

eObjectType& CObject::GetObjectType()
{
    return mType;
}

eObjectID& CObject::GetObjectID()
{
    return mID;
}

string& CObject::GetName()
{
    return mName;
}


void CObject::SetObjectType(eObjectType type)
{
	mType = type;
}

void CObject::SetObjectID(eObjectID id)
{
	mID = id;
}

void CObject::SetName(string name)
{
	mName = name;
}

float& CObject::GetMass()
{
	return mMass;
}

float& CObject::GetDragCoeff()
{
	return mCD;
}

float& CObject::GetCrossSection()
{
	return mAe;
}

void CObject::SetMass(float mass)
{
	mMass = mass;
}

void CObject::SetDragCoeff(float cd)
{
	mCD = cd;
}
void CObject::SetCrossSection(float Ae)
{
	mAe = Ae;
}

CVector3& CObject::GetPosition()
{
    return mPosition;
}

CVector3& CObject::GetVelocity()
{
    return mVelocity;
}

CVector3& CObject::GetHeading()
{
    return mHeading;
}

CVector3& CObject::GetRotationAxis()
{
    return mRotationAxis;
}

float& CObject::GetRotationAngle()
{
    return mRotationAngle;
}

void CObject::SetPosition(CVector3& pos)
{
	mPosition = pos;
}

void CObject::SetVelocity(CVector3& vel)
{
	mVelocity = vel;
}

void CObject::SetHeading(CVector3& heading)
{
	mHeading = heading;
}

void CObject::SetRotationAxis(CVector3& axis)
{
	mRotationAxis = axis;
}

void CObject::SetRotationAngle(float angle)
{
	mRotationAngle = angle;
}

bool CObject::IsActive()
{
    return mIsActive;
}

void CObject::SetActive(bool active)
{
    mIsActive = active;
}

bool CObject::IsFalling()
{
    return mIsFalling;
}

void CObject::SetFalling(bool falling)
{
    mIsFalling = falling;
}

bool CObject::IsVisible()
{
	return mIsVisible;
}

void CObject::SetVisible(bool visible)
{
	mIsVisible = visible;
}

void CObject::SetCollisionShape(CollisionShapePtr cs)
{
	mCollisionShape = cs;
	mCollisionShape->SetParent(this);
}

void CObject::SetMeshInfo(CMeshInfo& meshInfo)
{
	mMeshInfo = meshInfo;
}

CollisionShapePtr CObject::GetCollisionShape()
{
	return mCollisionShape;
}

CMeshInfo& CObject::GetMeshInfo()
{
	return mMeshInfo;
}

void CObject::Move(IPhysicsSystem* physics)
{
	physics->MoveObject(this);
}

CollisionPair CObject::TestCollision(CollisionShapePtr other, bool &collided)
{
	CollisionPair pair = mCollisionShape->TestCollision(other, collided);

	return pair;
}

//------------------------------------------------------------------------------------------------------------------------------------------
// CScrptObject
//------------------------------------------------------------------------------------------------------------------------------------------

CScriptObject::CScriptObject(CObject *obj) 
{ 
	//can't pass NULL as code-side object!
	assert(obj);
	// after initialization, the script-side object is guaranteed to live as long as its 
	// code-side counterpart (or "parent") lives. Even if by some mistake the parent gets 
	// deleted before the child, mCodeObj won't be set to NULL all the same, and will remain as 
	// a dangling pointer. This is why no check on mCodeObj != NULL will be made inside the methods.
	this->mCodeObj = obj; 
	mRefCount = 1;
}
	
int CScriptObject::AddRef() 
{ 
	return ++mRefCount; 
}
	
int CScriptObject::Release() 
{ 
	if( --mRefCount == 0 ) 
	{ 
		delete this; 
		return 0; 
	} 
		return mRefCount; 
}

eObjectType CScriptObject::GetType()
{
	return mCodeObj->GetObjectType();
}

eObjectID CScriptObject::GetID()
{
	return mCodeObj->GetObjectID();
}

string CScriptObject::GetName()
{
	return mCodeObj->GetName();
}
	
CVector3 CScriptObject::GetPosition()
{
	return mCodeObj->GetPosition();
}
 
CVector3 CScriptObject::GetVelocity()
{
	return mCodeObj->GetVelocity();
}

void CScriptObject::SetPosition(CVector3& pos)
{
	mCodeObj->GetPosition() = pos;
}

void CScriptObject::SetVelocity(CVector3& vel)
{
	mCodeObj->GetVelocity() = vel;
}

bool CScriptObject::IsActive()
{
	return mCodeObj->IsActive();
}

void CScriptObject::SetActive(bool active)
{
	mCodeObj->SetActive(active);
}

bool CScriptObject::IsFalling()
{
	return mCodeObj->IsFalling();
}

void CScriptObject::SetFalling(bool falling)
{
	mCodeObj->SetFalling(falling);
}
